﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FormsImageCompositor.cs" company="Jarno Burger">
//   See copyright.txt in the root of this project.
// </copyright>
// <summary>
//   Defines the FormsImageCompositor type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace WinFormAdapter
{
    using System;
    using System.Diagnostics;
    using System.Drawing;
    using System.IO;
    using System.Windows.Forms;

    using GUI4UFramework.Colors;
    using GUI4UFramework.Graphics;
    using GUI4UFramework.Management;
    using GUI4UFramework.Structural;

    using WinFormAdapter.Resources;

    /// <summary>
    /// This is the image compositor. This draws the elements on the screen.
    /// </summary>
    public sealed class FormsImageCompositor : ImageCompositor, IDisposable
    {
        /// <summary>
        /// Contains the resource pool with bitmaps.
        /// </summary>
        private readonly ResourcesBitmap resourcesBitmap;

        /// <summary>
        /// The game that has main logic.
        /// </summary>
        private readonly Game game;

        /// <summary>
        /// Contains the resource pool with fonts.
        /// </summary>
        private readonly ResourcesFont resourcesFont;

        /// <summary>
        /// The brush that is used for drawing.
        /// </summary>
        private readonly SolidBrush brush;

        /// <summary>The directory where all content resides.</summary>
        private readonly string contentDirectory;

        /// <summary>
        /// Flag: Has Dispose already been called?.
        /// </summary>
        private bool disposed;

        /// <summary>Initializes a new instance of the <see cref="FormsImageCompositor"/> class.</summary>
        /// <param name="game">The game to create this item.</param>
        /// <param name="contentDirectoryUrl">The content directory URL.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#")]
        public FormsImageCompositor(Game game, string contentDirectoryUrl)
        {
            this.resourcesBitmap = new ResourcesBitmap();
            this.resourcesFont = new ResourcesFont();
            this.game = game;

            this.brush = new SolidBrush(Color.Orange);
            this.contentDirectory = contentDirectoryUrl;
        }

        /// <summary>
        /// Creates a rectangle texture.
        /// </summary>
        /// <param name="preferredName">The preferred name to use.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="borderWidth">Width of the border.</param>
        /// <param name="fillColor">Color of the fill.</param>
        /// <param name="borderColor">Color of the border.</param>
        /// <returns>
        /// The URL to the created texture resource that looks like a rectangle.
        /// </returns>
        public override string CreateRectangleTexture(string preferredName, int width, int height, int borderWidth, GUIColor fillColor, GUIColor borderColor)
        {
#if DEBUG
            if (width <= 0)
            {
                throw new ArgumentException(@"width can not be zero or less", "width");
            }

            if (height <= 0)
            {
                throw new ArgumentException(@"height can not be zero or less", "height");
            }

            if (string.IsNullOrEmpty(preferredName))
            {
                throw new ArgumentException(@"Control name can not be null", "preferredName");
            }

            if (borderWidth < 0)
            {
                throw new ArgumentException(@"Border width can not be smaller then zero", "borderWidth");
            }
#endif

            var textureName = this.resourcesBitmap.Create(preferredName, width, height, fillColor);

            // var colordata = RectangleBrush.CreateRectangleColorMap(width, height,borderWidth,1,1, fillColor, borderColor);
            var colordata = ColorMapDrawer.CreateRectangleColorMap(width, height, borderWidth, fillColor, borderColor);

            this.resourcesBitmap.Update(textureName, colordata.ToArray());

            return textureName;
        }

        /// <summary>
        /// Creates a sprite font, to be used for text rendering.
        /// </summary>
        /// <param name="spriteFontName">Name of the sprite font in your Content.</param>
        public override void CreateSpriteFont(string spriteFontName)
        {
            if (string.IsNullOrEmpty(spriteFontName))
            {
                System.Diagnostics.Debug.WriteLine("The font name cannot be NULL or empty");
                Debugger.Break();
            }

            this.resourcesFont.CreateFromFontName(spriteFontName);
        }

        /// <summary>Creates the image texture.</summary>
        /// <param name="preferredName">Name of the base.</param>
        /// <param name="imageLocation">The file location for the image.</param>
        /// <returns>A texture with a image inside.</returns>
        /// <exception cref="Exception">Image path is wrong.</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
        public override string CreateImageTexture(string preferredName, string imageLocation)
        {
#if DEBUG
            if (imageLocation == null)
            {
                throw new ArgumentNullException("imageLocation", @"The path to the file can not be Null.");
            }
#endif

            var files = Directory.GetFiles(this.contentDirectory, "*.*", SearchOption.AllDirectories);
            string name = string.Empty;
            foreach (var file in files)
            {
                System.Diagnostics.Debug.WriteLine(file);
                if (file.ToUpper().Contains(imageLocation.ToUpper()))
                {
                    name = file;
                }
            }

            if (string.IsNullOrEmpty(name))
            {
                throw new Exception(string.Format("Image path {0} is not point to a file.", imageLocation));
            }

            var textureName = this.resourcesBitmap.CreateFromUrl(preferredName, name);
            return textureName;
        }

        /// <summary>Creates a flat texture with one color.</summary>
        /// <param name="preferredName">The preferred name to use.</param>
        /// <param name="width">The width of the line texture.</param>
        /// <param name="height">The height of the line texture.</param>
        /// <param name="color">The fill color of the line texture.</param>
        /// <returns>The name to use to find the texture back.</returns>
        public override string CreateFlatTexture(string preferredName, int width, int height, GUIColor color)
        {
            var imageName = this.resourcesBitmap.Create(preferredName, width, height, color);

            return imageName;
        }

        /// <summary>
        /// Reads the size string.
        /// </summary>
        /// <param name="spriteFontName">Name of the sprite font.</param>
        /// <param name="text">The text to use to measure the size.</param>
        /// <returns>The size of the text.</returns>
        public override DVector2 ReadSizeString(string spriteFontName, string text)
        {
            var stringFont = this.resourcesFont.Read(spriteFontName);

            // Measure string.
            var size = TextRenderer.MeasureText(text, stringFont);

            ////var stringSize = _game.CurrentGraphics.MeasureString(text, stringFont);

            return new DVector2(size.Width, size.Height);
        }

        /// <summary>
        /// Reads the size texture.
        /// </summary>
        /// <param name="textureName">The name of the texture location.</param>
        /// <returns>The size of the texture.</returns>
        public override DVector2 ReadSizeTexture(string textureName)
        {
            var image = this.resourcesBitmap.Read(textureName);
            return new DVector2(image.Width, image.Height);
        }

        /// <summary>
        /// Updates the texture color image with give color image.
        /// </summary>
        /// <param name="textureName">Name of the texture.</param>
        /// <param name="colorMap">The color array.</param>
        public override void UpdateTexture(string textureName, ColorMap colorMap)
        {
#if DEBUG
            if (colorMap == null)
            {
                throw new ArgumentNullException("colorMap", @"ColorMap is used to update the texture, can not be null.");
            }
#endif

            this.resourcesBitmap.Update(textureName, colorMap.ToArray());
        }

        /// <summary>
        /// Draws the string.
        /// </summary>
        /// <param name="fontName">The sprite font.</param>
        /// <param name="text">The text to draw.</param>
        /// <param name="position">The position.</param>
        /// <param name="fontColor">Color of the font.</param>
        public override void DrawString(string fontName, string text, DVector2 position, GUIColor fontColor)
        {
            var g = this.game.CurrentGraphics;
            var font = this.resourcesFont.Read(fontName);
            this.brush.Color = Color.FromArgb(fontColor.A, fontColor.R, fontColor.G, fontColor.B);
            var point = new PointF(position.X, position.Y);

            g.DrawString(text, font, this.brush, point);
        }

        /// <summary>Creates a render target to render onto.</summary>
        /// <param name="preferredName">Name of the base.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="mipMap">If set to <c>true</c> [mip map].</param>
        /// <returns>A render target.</returns>
        public override string CreateRenderTarget(string preferredName, int width, int height, bool mipMap)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Updates the texture with given render-target.
        /// </summary>
        /// <param name="textureName">Name of the texture.</param>
        /// <param name="renderTargetName">Name of the render target.</param>
        public override void UpdateTexture(string textureName, string renderTargetName)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Deletes the specified resource from the resource pool.
        /// </summary>
        /// <param name="resourceName">Name of the resource.</param>
        public override void Delete(string resourceName)
        {
            if (this.resourcesBitmap.Contains(resourceName))
            {
                this.resourcesBitmap.Delete(resourceName);
                return;
            }

            if (this.resourcesFont.Contains(resourceName))
            {
                this.resourcesFont.Delete(resourceName);
                return;
            }

            System.Diagnostics.Debug.WriteLine("Could not delete " + resourceName);
            Debugger.Break();
        }

        /// <summary>
        /// Notes the beginning of drawing the textures. This is for the engine a important signal.
        /// </summary>
        public override void BeginDraw()
        {
        }

        /// <summary>
        /// Draws the specified texture using specified draw-state , with a tint color.
        /// </summary>
        /// <param name="name">The name of the item to draw.</param>
        /// <param name="drawState">The state that contains all the info to draw the item.</param>
        /// <param name="tintColor">The color to use to tint the shown item.</param>
        public override void Draw(string name, DrawState drawState, GUIColor tintColor)
        {
#if DEBUG
            if (drawState == null)
            {
                throw new ArgumentNullException("drawState", @"The draw-state is used to draw , it can not be null.");
            }
#endif
            // get the texture to draw
            var texture = this.resourcesBitmap.Read(name);

            // create the source-rectangle

            RectangleF srcRect;
            if (drawState.SourceRectangle == null)
            {
                srcRect = new RectangleF(0, 0, texture.Width, texture.Height);
            }
            else
            {
                srcRect = new RectangleF((int)drawState.SourceRectangle.PositionX, (int)drawState.SourceRectangle.PositionY, (int)drawState.SourceRectangle.Width, (int)drawState.SourceRectangle.Height);
            }

            // create the target-rectangle
            // var scaleVector = new DVector2(drawState.Width / texture.Width, drawState.Height / texture.Height);
            var position = new Point((int)(drawState.DrawPosition.X + drawState.Offset.X), (int)(drawState.DrawPosition.Y + drawState.Offset.Y));
            var destRect = new RectangleF(position.X, position.Y, drawState.Width, drawState.Height);

            // create the unit size
            this.game.CurrentGraphics.DrawImage(texture, destRect, srcRect, GraphicsUnit.Pixel);
            ////this.game.CurrentGraphics.DrawImageUnscaled(texture, position);
        }

        /// <summary>
        /// Ends the drawing of textures. This is for the engine a important signal.
        /// </summary>
        public override void EndDraw()
        {
        }

        /// <summary>
        /// Sets the render target to given target-name.
        /// </summary>
        /// <param name="renderTargetName">Name of the render target.</param>
        public override void SetRenderTarget(string renderTargetName)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Clears the current render target with given color.
        /// </summary>
        /// <param name="clearColor">Color of the clear.</param>
        public override void Clear(GUIColor clearColor)
        {
            throw new NotImplementedException();
        }

        /// <summary>Unsets the render target.</summary>
        /// <param name="renderTargetName">Name of the render target.</param>
        public override void UnsetRenderTarget(string renderTargetName)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Debugs this instance. Making it spit out values of current state of this compositor.
        /// </summary>
        public override void Debug()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Determines whether a resource pool [contains] [the specified asset name].
        /// </summary>
        /// <param name="assetName">Name of the asset.</param>
        /// <returns>
        /// Whether we have the asset (True) or not (False).
        /// </returns>
        public override bool Contains(string assetName)
        {
            if (this.resourcesBitmap.Contains(assetName))
            {
                return true;
            }

            if (this.resourcesFont.Contains(assetName))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// Public implementation of Dispose pattern callable by consumers. 
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            // ReSharper disable once GCSuppressFinalizeForTypeWithoutDestructor
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// Protected implementation of Dispose pattern. 
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposing)
        {
            if (this.disposed)
            {
                return;
            }

            if (disposing)
            {
                //// Free any other managed objects here. 
                this.brush.Dispose();
            }

            //// Free any unmanaged objects here. 
            this.disposed = true;
        }
    }
}